

namespace mShare.DMAP {


	public enum AuthMethod {
		NONE = 0,
		NAME_AND_PASSWORD = 1,
		PASSWORD   = 2
	}

	public class DictionaryItem : SerializableObject {
		public override string get_code() {
			return "dmap.dictionaryitem";
		}
	}

	public class ContentCodeDictionary : DictionaryItem {
		private ContentCode content_code;

		public int dmap_contentcodesnumber { get; set; default = 0; }
		public string dmap_contentcodesname {
			get {
				return content_code.name;
			}
		}
		public uint32 dmap_contentcodestype {
			get {
				return (uint32) content_code.type;
			}
		}

		public ContentCodeDictionary(ContentCode content_code) {
			this.content_code = content_code;
		}
	}

	public class ContentCodes : SerializableObject, ServerResponse {
		public override string get_code() {
			return "dmap.contentcodesresponse";
		}

		public override Gee.Iterable<Structure> get_serializeable_children() {
			var properties = new Gee.LinkedList<Structure>();
			foreach (unowned ContentCode cc in ContentCode.CODES) {
				var item = new ContentCodeDictionary(cc);
				properties.add(new Structure.from_cc(item.get_content_code(), item));
			}
			return properties;
		}
	}

	public class ServerInfo : SerializableObject, ServerResponse {

		public override string get_code() {
			return "dmap.serverinforesponse";
		}

		public float dmap_protocolversion    { get; set; default = 1.0f; }
		public string dmap_itemname          { get; set; default = ""; }
		public AuthMethod dmap_authenticationmethod { get; set; default = AuthMethod.NONE; }
		public int dmap_loginrequired        { get; set; default = 0; }
		public int dmap_timeoutinterval      { get; set; default = 1000; }
		public int dmap_supportsautologout   { get; set; default = 0; }
		public int dmap_supportsupdate       { get; set; default = 1; }
		public int dmap_supportspersistenids { get; set; default = 0; }
		public int dmap_supportsextensions   { get; set; default = 0; }
		public int dmap_supportsbrowse       { get; set; default = 0; }
		public int dmap_supportsquery        { get; set; default = 0; }
		public int dmap_supportsindex        { get; set; default = 0; }
		public int dmap_supportsresolve      { get; set; default = 0; }
		public int dmap_databasescount       { get; set; default = 1; }
	}

	public class PlaylistItem : ListingItem {
		public IMediaContainer container { get; private set; }

		public uint dmap_itemcount { 
			get {
				return container.item_count;
			}
		}
		public bool dmap_baseplaylist { get; set; default = false; }

		public PlaylistItem(IMediaContainer container, bool baseplaylist = false) {
			this.container = container;
			this.dmap_baseplaylist = baseplaylist;
		}
	}

	public class DatabasePlaylists : ListingResponse {
		/* APLY database playlists
		 *      MSTT status
		 *      MUTY update type
		 *      MTCO specified total count
		 *      MRCO returned count
		 *      MLCL listing
		 *              MLIT listing item
		 *                      MIID item id
		 *                      MPER persistent item id
		 *                      MINM item name
		 *                      MIMC item count
		 *                      ABPL baseplaylist (only for base)
		 *              MLIT
		 *              ...
		 */

		public IMediaContainer db { get; private set; }
		
		public uint dmap_specifiedtotalcount {
			get {
				return db.container_count;
			}
			set {}
		}
		public uint dmap_returnedcount {
			get {
				return db.container_count;
			}
			set {}
		}

		public DatabasePlaylists (IMediaContainer db) {
			this.db = db;
		}

		public override string get_code() {
			return "aply";
		}

		public override Gee.Iterable<ListingItem> get_children() {
			var playlists = new Gee.ArrayList<ListingItem>();
			playlists.add(new PlaylistItem(db, true));
			foreach (IMediaContainer container in db.list_containers(0, 0, null)) {
				playlists.add(new PlaylistItem(container));
			}
			return playlists;
		}
	}

	/*
	public struct ServerHandlerInfo {
		Soup.Server server;
		Soup.Message msg;
		string path;
		HashTable ?query;
		Soup.ClientContext client;
	}

	delegate ServerResponse ServerHandler(ServerHandlerInfo server_info);*/

	public class Share : Object {
		private HashTable<uint32, string> session_ids;
		//private Zeroconf.Publisher publisher;
	
		public bool server_active;
	
		public Soup.Server server { get; private set; }
		public string[] txt_records { get; set; }
		public uint port { get; private set; }
		public string name { get; set; }
		public string password { get; set; }
		public uint revision_number { get; set; }
		public AuthMethod auth_method { get; set; }

		public ServerInfo server_info;
		
		public Share() {
			//this.publisher = new Zeroconf.AvahiPublisher();

			server_info = new ServerInfo();
		}
	
		private void debug_msg(Soup.Message msg, string path, HashTable ?query) {
			debug ("Path: %s\n", path);
			//foreach (string v in GLib.Iterator(query)) print(v);
			//if (query != null)
			//	query.foreach((k, v) => {
			//		debug ("(%s => %s)\n", (string)k, (string)v);
			//	});
		}
	
		public void server_info_handler(Soup.Server server, Soup.Message msg, string path, 
			                        HashTable ?query, Soup.ClientContext client) {
			debug_msg(msg, path, query);


		}

		public void content_codes_handler(Soup.Server server, Soup.Message msg, string path, 
			                        HashTable ?query, Soup.ClientContext client) {
			debug ("Path: %s\n", path);
		}

		public void login_handler(Soup.Server server, Soup.Message msg, string path, 
			                        HashTable ?query, Soup.ClientContext client) {
			debug ("Path: %s\n", path);
			
			uint32 session_id = this.session_id_create(client);
			/*var response = new Structure.from_container("dmap.loginresponse", (node) => {
				node.add("dmap.status", 200);
				node.add("dmap.sessionid", session_id);
			});
			response.print();*/
		}

		public void logout_handler(Soup.Server server, Soup.Message msg, string path, 
			                        HashTable ?query, Soup.ClientContext client) {
			debug ("Path: %s\n", path);
		}

		public void update_handler(Soup.Server server, Soup.Message msg, string path, 
			                        HashTable ?query, Soup.ClientContext client) {
			debug ("Path: %s\n", path);
		}

		public void databases_handler(Soup.Server server, Soup.Message msg, string path, 
			                      HashTable ?query, Soup.ClientContext client) {
			debug ("Path: %s\n", path);
			
			var response = server_info.serialize();

			response.print();
			var stream = new MemoryOutputStream(null, realloc, free);
			if (!response.serialize(stream)) {
				warning("Could not serialize response");
				return;
			}
			debug("Serialized");
			unowned uint8[] data = stream.get_data();
			data.length = (int32)stream.get_data_size();
			var in_stream = new MemoryInputStream.from_data(data, free);
			try {
				Structure.parse(in_stream).print();
			} catch (Serialize.Error e) {
				print("Unable to parse structure: %s", e.message);
			}
									
			//	mlcl.add_nodes("mlit", (mlit) => {
			//		mlit.add_from_name("miid", null);
			//	});
			//});
			
			/*
			avdb = dmap_structure_add (NULL, DMAP_CC_AVDB);
			dmap_structure_add (avdb, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
			dmap_structure_add (avdb, DMAP_CC_MUTY, 0);
			dmap_structure_add (avdb, DMAP_CC_MTCO, (gint32) 1);
			dmap_structure_add (avdb, DMAP_CC_MRCO, (gint32) 1);
			mlcl = dmap_structure_add (avdb, DMAP_CC_MLCL);
			mlit = dmap_structure_add (mlcl, DMAP_CC_MLIT);
			dmap_structure_add (mlit, DMAP_CC_MIID, (gint32) 1);
			dmap_structure_add (mlit, DMAP_CC_MPER, (gint64) 1);
			dmap_structure_add (mlit, DMAP_CC_MINM, nameprop);
			dmap_structure_add (mlit, DMAP_CC_MIMC, dmap_db_count (share->priv->db));
			dmap_structure_add (mlit, DMAP_CC_MCTC, (gint32) 1);*/
		}

		public void ctrl_int_handler(Soup.Server server, Soup.Message msg, string path, 
			                        HashTable ?query, Soup.ClientContext client) {
			debug ("Path: %s\n", path);
		}
	
		public virtual ServerInfo get_server_info() {
			return this.server_info;
		}

		public void handler(Soup.Server server, Soup.Message msg, string path, HashTable? query,
		                    Soup.ClientContext client) {
			warning("Path: %s", path);
			if (Regex.match_simple("^/server-info$", path)) {
				send_structure(server, msg, this.get_server_info().serialize());
			} else if (Regex.match_simple("^/databases$", path)) {
				//send_structure(server, msg, DatabasePlaylists(db));
			}
		}

		public void send_structure(Soup.Server server, Soup.Message msg, Structure structure) {
		
		}

		public bool server_start() {
			int port = this.desired_port();
			this._server = new Soup.Server(Soup.SERVER_PORT, port);
			if (this._server == null) {
				warning ("Unable to start music sharing server on port %d, trying any open port", port);
				this._server = new Soup.Server(Soup.SERVER_PORT, Soup.ADDRESS_ANY_PORT);
				if (this._server == null)
					return false;
			}
			this._port = this._server.get_port();
			debug ("Started DMAP server on port %u", this._port);

			this._server.add_handler ("/", this.handler);
		
			/*
			this._server.add_handler ("/server-info", this.server_info_handler);
			this._server.add_handler ("/content-codes", this.content_codes_handler);
			this._server.add_handler ("/login", this.login_handler);
			this._server.add_handler ("/logout", this.logout_handler);
			this._server.add_handler ("/update", this.update_handler);
			this._server.add_handler ("/databases", this.databases_handler);
			this._server.add_handler ("/ctrl-int", this.ctrl_int_handler);*/
		
			this.server_active = true;
			this.session_ids = new GLib.HashTable<uint32,string>.full(direct_hash, direct_equal, null, g_free);
		
			this._server.run_async();
		
			return true;
		}
	
		public bool server_stop() {
			debug ("Stopping music sharing server on port %u", this._port);

			if (this._server != null) {
				this._server.quit();
				this._server = null;
			}
		
			this.session_ids = null;
			this.server_active = false;
		
			return true;
		}
		
		public void restart() {
			if (this.server_start())
				this.publish_start();
			else
				this.publish_stop();
				
		}
	
		public bool publish_start() {
			/*try {
				this.publisher.publish(this.name, this._port, 
					                   this.get_type_of_service(),
				    	               (this.auth_method != AuthMethod.NONE), 
				        	           this.txt_records);
			} catch (Zeroconf.Error e) {
				warning("Could not start Zeroconf publishing: %s", e.message);
				return false;
			}*/
			return true;
		}
		
		public bool publish_stop() {
			/*try {
				this.publisher.withdraw(this._port);
			} catch (Zeroconf.Error e) {
				warning("Could not stop Zeroconf publishing: %s", e.message);
				return false;
			}*/
			return true;
		}
	
		public virtual int desired_port() {
			return 8080;
		}
	
		public virtual string get_type_of_service() {
			return "";
		}
		
		protected bool get_session_id(HashTable<string,string> query, out uint32 session_id) {
			string session_id_query = query.lookup("session-id");
			if (session_id_query == null) {
				warning("Session id not found");
				return false;
			}
			session_id_query.scanf("%d", out session_id);
			return true;
		}
		
		protected bool session_id_validate(Soup.ClientContext context, Soup.Message message, 
			HashTable<string, string> query, out uint32 id) {
			if (!this.get_session_id(query, out id))
				return false;
			string addr = this.session_ids.lookup(id);
			if (addr == null)
				return false;
			string? remote_addr = context.get_host();
			if (remote_addr == addr)
				return true;
			else
				return false;
		}
		
		protected uint32 session_id_create(Soup.ClientContext context) {
			uint32 id;
			while (true) {
				id = Random.next_int();
				if (this.session_ids.lookup(id) == null)
					break;
			}
			this.session_ids.insert(id, context.get_host());
			return id;
		}
	}
}